// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <ddk/debug.h>

#include "xdc.h"
#include "xdc-transfer.h"

namespace usb_xhci {

// Reads a range of bits from an integer.
#define READ_FIELD(i, start, bits) (((i) >> (start)) & ((1 << (bits)) - 1))

static void xdc_ring_doorbell(xdc_t* xdc, xdc_endpoint_t* ep) {
  uint8_t doorbell_val = ep->direction == USB_DIR_IN ? DCDB_DB_EP_IN : DCDB_DB_EP_OUT;
  XHCI_SET_BITS32(&xdc->debug_cap_regs->dcdb, DCDB_DB_START, DCDB_DB_BITS, doorbell_val);
}

// Stores the value of the Dequeue Pointer into out_dequeue.
// Returns ZX_OK if successful, or ZX_ERR_BAD_STATE if the endpoint was not in the Stopped state.
static zx_status_t xdc_get_dequeue_ptr_locked(xdc_t* xdc, xdc_endpoint_t* ep, uint64_t* out_dequeue)
    __TA_REQUIRES(xdc->lock) {
  if (ep->state != XDC_EP_STATE_STOPPED) {
    zxlogf(ERROR, "tried to read dequeue pointer of %s EP while not stopped, state is: %d\n",
           ep->name, ep->state);
    return ZX_ERR_BAD_STATE;
  }
  xdc_context_data_t* ctx = xdc->context_data;
  xhci_endpoint_context_t* epc = ep->direction == USB_DIR_OUT ? &ctx->out_epc : &ctx->in_epc;

  uint64_t dequeue_ptr_hi = XHCI_READ32(&epc->tr_dequeue_hi);
  uint32_t dequeue_ptr_lo = XHCI_READ32(&epc->epc2) & EP_CTX_TR_DEQUEUE_LO_MASK;
  *out_dequeue = (dequeue_ptr_hi << 32 | dequeue_ptr_lo);
  return ZX_OK;
}

// Returns ZX_OK if the request was scheduled successfully, or ZX_ERR_SHOULD_WAIT
// if we ran out of TRBs.
static zx_status_t xdc_schedule_transfer_locked(xdc_t* xdc, xdc_endpoint_t* ep, usb_request_t* req)
    __TA_REQUIRES(xdc->lock) {
  xhci_transfer_ring_t* ring = &ep->transfer_ring;

  // Need to clean the cache for both IN and OUT transfers, invalidate only for IN.
  if (ep->direction == USB_DIR_IN) {
    usb_request_cache_flush_invalidate(req, 0, req->header.length);
  } else {
    usb_request_cache_flush(req, 0, req->header.length);
  }

  zx_status_t status = xhci_queue_data_trbs(ring, &ep->transfer_state, req, 0 /* interrupter */,
                                            false /* isochronous */);
  if (status != ZX_OK) {
    return status;
  }

  // If we get here, then we are ready to ring the doorbell.
  // Save the ring position so we can update the ring dequeue ptr once the transfer completes.
  xdc_req_internal_t* req_int = USB_REQ_TO_XDC_INTERNAL(req, sizeof(usb_request_t));
  req_int->context = ring->current_trb;
  xdc_ring_doorbell(xdc, ep);

  return ZX_OK;
}

// Schedules any queued requests on the endpoint's transfer ring, until we fill our
// transfer ring or have no more requests.
void xdc_process_transactions_locked(xdc_t* xdc, xdc_endpoint_t* ep) __TA_REQUIRES(xdc->lock) {
  uint64_t usb_req_size = sizeof(usb_request_t);
  zx_status_t status;
  while (1) {
    if (xhci_transfer_ring_free_trbs(&ep->transfer_ring) == 0) {
      // No available TRBs - need to wait for some to complete.
      return;
    }

    if (!ep->current_req) {
      // Start the next transaction in the queue.
      usb_request_t* req = xdc_req_list_remove_head(&ep->queued_reqs, usb_req_size);
      if (!req) {
        // No requests waiting.
        return;
      }
      xhci_transfer_state_init(&ep->transfer_state, req, USB_ENDPOINT_BULK, EP_CTX_MAX_PACKET_SIZE);
      status = xdc_req_list_add_tail(&ep->pending_reqs, req, usb_req_size);
      ZX_DEBUG_ASSERT(status == ZX_OK);
      ep->current_req = req;
    }

    usb_request_t* req = ep->current_req;
    status = xdc_schedule_transfer_locked(xdc, ep, req);
    if (status == ZX_ERR_SHOULD_WAIT) {
      // No available TRBs - need to wait for some to complete.
      return;
    } else {
      ep->current_req = nullptr;
    }
  }
}

zx_status_t xdc_queue_transfer(xdc_t* xdc, usb_request_t* req, bool in, bool is_ctrl_msg) {
  xdc_endpoint_t* ep = in ? &xdc->eps[IN_EP_IDX] : &xdc->eps[OUT_EP_IDX];

  mtx_lock(&xdc->lock);

  // We should always queue control messages unless there is an unrecoverable error.
  if (!is_ctrl_msg && (!xdc->configured || ep->state == XDC_EP_STATE_DEAD)) {
    mtx_unlock(&xdc->lock);
    return ZX_ERR_IO_NOT_PRESENT;
  }

  if (req->header.length > 0) {
    zx_status_t status = usb_request_physmap(req, xdc->bti_handle);
    if (status != ZX_OK) {
      zxlogf(ERROR, "%s: usb_request_physmap failed: %d\n", __FUNCTION__, status);
      mtx_unlock(&xdc->lock);
      return status;
    }
  }

  xdc_req_internal_t* req_int = USB_REQ_TO_XDC_INTERNAL(req, sizeof(usb_request_t));
  req_int->complete_cb.callback = in ? xdc_read_complete : xdc_write_complete;
  req_int->complete_cb.ctx = xdc;

  list_add_tail(&ep->queued_reqs, &req_int->node);

  // We can still queue requests for later while waiting for the xdc device to be configured,
  // or while the endpoint is halted. Before scheduling the TRBs however, we should wait
  // for the device to be configured, and/or the halt is cleared by DbC and we've cleaned
  // up the transfer ring.
  if (xdc->configured && ep->state == XDC_EP_STATE_RUNNING) {
    xdc_process_transactions_locked(xdc, ep);
  }

  mtx_unlock(&xdc->lock);

  return ZX_OK;
}

bool xdc_has_free_trbs(xdc_t* xdc, bool in) {
  mtx_lock(&xdc->lock);

  xdc_endpoint_t* ep = in ? &xdc->eps[IN_EP_IDX] : &xdc->eps[OUT_EP_IDX];
  bool has_trbs = xhci_transfer_ring_free_trbs(&ep->transfer_ring) > 0;

  mtx_unlock(&xdc->lock);
  return has_trbs;
}

zx_status_t xdc_restart_transfer_ring_locked(xdc_t* xdc, xdc_endpoint_t* ep) {
  // Once the DbC clears the halt flag for the endpoint, the address stored in the
  // TR Dequeue Pointer field is the next TRB to be executed (see XHCI Spec 7.6.4.3).
  // There seems to be no guarantee which TRB this will point to.
  //
  // The easiest way to deal with this is to convert all scheduled TRBs to NO-OPs,
  // and reschedule pending requests.

  uint64_t dequeue_ptr;
  zx_status_t status = xdc_get_dequeue_ptr_locked(xdc, ep, &dequeue_ptr);
  if (status != ZX_OK) {
    return status;
  }
  xhci_transfer_ring_t* ring = &ep->transfer_ring;
  xhci_trb_t* trb = xhci_transfer_ring_phys_to_trb(ring, dequeue_ptr);
  if (!trb) {
    zxlogf(ERROR, "no valid TRB corresponding to dequeue_ptr: %lu\n", dequeue_ptr);
    return ZX_ERR_BAD_STATE;
  }

  // Reset our copy of the dequeue pointer.
  xhci_set_dequeue_ptr(ring, trb);

  // Convert all pending TRBs on the transfer ring into NO-OPs TRBs.
  // ring->current is just after our last queued TRB.
  xhci_trb_t* last_trb = nullptr;
  while (trb != ring->current_trb) {
    xhci_set_transfer_noop_trb(trb);
    last_trb = trb;
    trb = xhci_get_next_trb(ring, trb);
  }
  if (last_trb) {
    // Set IOC (Interrupt on Completion) on the last NO-OP TRB, so we know
    // when we can overwrite them in the transfer ring.
    uint32_t control = XHCI_READ32(&last_trb->control);
    XHCI_WRITE32(&last_trb->control, control | XFER_TRB_IOC);
  }
  // Restart the transfer ring.
  xdc_ring_doorbell(xdc, ep);
  ep->state = XDC_EP_STATE_RUNNING;

  // Requeue and reschedule the requests.
  usb_request_t* req;
  uint64_t usb_req_size = sizeof(usb_request_t);
  while ((req = xdc_req_list_remove_tail(&ep->pending_reqs, usb_req_size)) != nullptr) {
    status = xdc_req_list_add_head(&ep->queued_reqs, req, usb_req_size);
    ZX_DEBUG_ASSERT(status == ZX_OK);
  }
  xdc_process_transactions_locked(xdc, ep);
  return ZX_OK;
}

void xdc_handle_transfer_event_locked(xdc_t* xdc, xdc_poll_state_t* poll_state, xhci_trb_t* trb) {
  uint32_t control = XHCI_READ32(&trb->control);
  uint32_t status = XHCI_READ32(&trb->status);
  uint32_t ep_dev_ctx_idx = READ_FIELD(control, TRB_ENDPOINT_ID_START, TRB_ENDPOINT_ID_BITS);
  uint8_t xdc_ep_idx = ep_dev_ctx_idx == EP_IN_DEV_CTX_IDX ? IN_EP_IDX : OUT_EP_IDX;
  xdc_endpoint_t* ep = &xdc->eps[xdc_ep_idx];
  xhci_transfer_ring_t* ring = &ep->transfer_ring;
  uint64_t usb_req_size = sizeof(usb_request_t);

  uint32_t cc = READ_FIELD(status, EVT_TRB_CC_START, EVT_TRB_CC_BITS);
  uint32_t length = READ_FIELD(status, EVT_TRB_XFER_LENGTH_START, EVT_TRB_XFER_LENGTH_BITS);
  usb_request_t* req = nullptr;
  bool error = false;

  switch (cc) {
    case TRB_CC_SUCCESS:
    case TRB_CC_SHORT_PACKET:
      break;
    case TRB_CC_BABBLE_DETECTED_ERROR:
    case TRB_CC_USB_TRANSACTION_ERROR:
    case TRB_CC_TRB_ERROR:
    case TRB_CC_STALL_ERROR:
      zxlogf(ERROR, "xdc_handle_transfer_event: error condition code: %d\n", cc);
      error = true;
      break;
    default:
      zxlogf(ERROR, "xdc_handle_transfer_event: unexpected condition code %d\n", cc);
      error = true;
      break;
  }

  // Even though the main poll loop checks for changes in the halt registers,
  // it's possible we missed the halt register being set if the halt was cleared fast enough.
  if (error) {
    if (ep->state == XDC_EP_STATE_RUNNING) {
      xdc_endpoint_set_halt_locked(xdc, poll_state, ep);
    }
    ep->got_err_event = true;
    // We're going to requeue the transfer when we restart the transfer ring,
    // so nothing else to do.
    return;
  }

  if (control & EVT_TRB_ED) {
    // An Event Data TRB generated the completion event, so the TRB Pointer field
    // will contain the usb request pointer we previously stored.
    req = reinterpret_cast<usb_request_t*>(trb->ptr);
  } else {
    // Get the pointer to the TRB that generated the event.
    trb = xhci_read_trb_ptr(ring, trb);
    if (trb_get_type(trb) == TRB_TRANSFER_NOOP) {
      // If it's the NO-OP TRB we queued when dealing with the halt condition,
      // there won't be a corresponding usb request.
      zxlogf(TRACE, "xdc_handle_transfer_event: got a NO-OP TRB\n");
      xhci_set_dequeue_ptr(ring, xhci_get_next_trb(ring, trb));
      xdc_process_transactions_locked(xdc, ep);
      return;
    }

    // Look for the Event Data TRB which will have the usb request pointer.
    for (uint i = 0; i < TRANSFER_RING_SIZE && trb; i++) {
      if (trb_get_type(trb) == TRB_TRANSFER_EVENT_DATA) {
        req = reinterpret_cast<usb_request_t*>(trb->ptr);
        break;
      }
      trb = xhci_get_next_trb(ring, trb);
    }
  }

  if (!req) {
    zxlogf(ERROR, "xdc_handle_transfer_event: unable to find request to complete\n");
    return;
  }

  // Find the usb request in the pending list.
  bool found_req = false;
  usb_request_t* test;
  xdc_req_internal_t* test_int;
  list_for_every_entry (&ep->pending_reqs, test_int, xdc_req_internal_t, node) {
    test = XDC_INTERNAL_TO_USB_REQ(test_int, usb_req_size);
    if (test == req) {
      found_req = true;
      break;
    }
  }
  if (!found_req) {
    zxlogf(ERROR, "xdc_handle_transfer_event: ignoring event for completed transfer\n");
    return;
  }
  // Remove request from pending_reqs.
  list_delete(&test_int->node);

  // Update our copy of the dequeue_ptr to the TRB following this transaction.
  xhci_set_dequeue_ptr(ring, static_cast<xhci_trb_t*>(test_int->context));
  xdc_process_transactions_locked(xdc, ep);

  // Save the request to be completed later out of the lock.
  req->response.status = ZX_OK;
  req->response.actual = length;
  status = xdc_req_list_add_tail(&poll_state->completed_reqs, req, usb_req_size);
  ZX_DEBUG_ASSERT(status == ZX_OK);
}

}  // namespace usb_xhci
